package me.sdau.spider.openapi.service;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import lombok.extern.slf4j.Slf4j;
import me.sdau.spider.openapi.dto.QueryDTO;
import me.sdau.spider.openapi.exception.QueryParamException;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.SimpleQueryStringBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.sort.SortParseElement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Service
public class ElasticSearchService {

    private static final String FIELD_NAME = "publish_time";

    private Pattern pattern = Pattern.compile("[1-2][0-9][0-9][0-9]-[0-1]{0,1}[0-9]-[0-3]{0,1}[0-9]");

    @Autowired
    private TransportClient transportClient;

    /**
     * 通过具体 ID 查询
     *
     * @param index
     * @param type
     * @param id
     * @return
     */
    public GetResponse getById(String index, String type, String id) {
        return transportClient.prepareGet(index, type, id).get();
    }

    public List<Map<String, Object>> queryBytime(String index, String type, String startTime, String endTime, String queryStr) {
        // 校验时间格式
        QueryDTO query = checkQuery(queryStr);
        QueryBuilder queryBuilder = buildQuery(startTime, endTime, query);
        SearchResponse response = getSearchResponse(index, type, queryBuilder);
        List<Map<String, Object>> result = new ArrayList<>(Math.toIntExact(response.getHits().getTotalHits()));
        while (true) {
            result.addAll(Stream
                    .of(response.getHits().getHits())
                    .parallel()
                    .map(SearchHit::getSource)
                    .collect(Collectors.toList()));
            /*Stream.of(response.getHits().getHits())
                    .parallel()
                    .forEach(p -> result.add(p.getSource()));*/
            response = transportClient
                    .prepareSearchScroll(response.getScrollId())
                    .setScroll(new TimeValue(6000))
                    .execute()
                    .actionGet();
            if (response.getHits().getHits().length == 0) {
                break;
            }
        }
        return result;
    }

    /**
     * 校验时间格式
     *
     * @param startTime
     * @param endTime
     */
    private boolean checkTime(String startTime, String endTime) throws QueryParamException {
        if (Strings.isNullOrEmpty(startTime) || Strings.isNullOrEmpty(endTime)) {
            return false;
        }
        Matcher matcherStart = pattern.matcher(startTime);
        Matcher matcherEnd = pattern.matcher(endTime);
        if (!matcherStart.lookingAt() || !matcherEnd.lookingAt()) {
            throw new QueryParamException("时间格式异常");
        }
        return true;
    }

    private QueryDTO checkQuery(String queryStr) throws QueryParamException {
        try {
            return SingletonGson.gson.fromJson(queryStr, QueryDTO.class);
        } catch (JsonSyntaxException e) {
            throw new QueryParamException(e.getMessage(), e);
        }
    }

    private SearchResponse getSearchResponse(String index, String type, QueryBuilder queryBuilder) {
        SearchRequestBuilder builder = transportClient
                .prepareSearch(index)
                .setTypes(type)
                .setSearchType(SearchType.DEFAULT)
                .addSort(SortParseElement.DOC_FIELD_NAME, SortOrder.ASC)
                .setQuery(queryBuilder)
                .setSize(200)
                .setScroll(new TimeValue(60000));
        log.debug("ES查询语句:\n[{}]", builder.toString());
        SearchResponse response = builder
                .execute()
                .actionGet();
        log.debug("总条数[{}]", Math.toIntExact(response.getHits().getTotalHits()));
        return response;
    }

    private QueryBuilder buildQuery(String startTime, String endTime, QueryDTO query) {
        if (!checkTime(startTime, endTime)) {
            // 全文查找
            if (query == null) {
                return QueryBuilders.matchAllQuery();
            }
            return buildSimpleQueryString(query);
        }

        if (query == null) {
            return QueryBuilders
                    .rangeQuery(FIELD_NAME)
                    .gte(startTime)
                    .lte(endTime);
        }

        return QueryBuilders
                .boolQuery()
                .must(buildSimpleQueryString(query))
                .must(QueryBuilders
                        .rangeQuery(FIELD_NAME)
                        .gte(startTime)
                        .lte(endTime));
    }

    /**
     * 构建简单字符串查找
     * @param query
     * @return
     */
    private SimpleQueryStringBuilder buildSimpleQueryString(QueryDTO query) {
        SimpleQueryStringBuilder queryStringBuilder = QueryBuilders
                .simpleQueryStringQuery(Optional
                        .ofNullable(query.getText())
                        .orElseThrow(() -> new QueryParamException("参数异常")));

        Optional.ofNullable(query.getField())
                .orElse(Lists.newArrayList())
                .forEach(queryStringBuilder::field);
        return queryStringBuilder;
    }


    private static class SingletonGson {
        private static final Gson gson = new Gson();
    }

}
